/************************************************************************
 * @file PluginDescriptionParser.cpp
 * @author Ian Chen
 * @version 1.0
 *
 * @section LICENSE
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details at
 * http://www.gnu.org/licenses/lgpl.html
 *
 ************************************************************************/
#include <QDir>
#include <QDomAttr>
#include <QDomElement>
#include <QDomNode>
#include <QDomNodeList>
#include <QFileInfoList>

#include <plugin/util/Framework.h>
#include <plugin/service/LoggerService.h>
#include <plugin/util/PluginDescriptionParser.h>

namespace antu
{
namespace plugin
{
/*
 * <plugin code='pluginCode' name='descriptive name' level='1' libName='pluginlib'>
 *   <version>1.0.0.0</version>
 *   <compatibleVersion>1.0.0.0</compatibleVersion>
 *   <appGroups>group1,group2,group3</appGroups>
 *   <requiredPlugins>
 *     <requirement code='code' version='1.0.0.0'>
 *       <services values='service1,service2'/>
 *     </requirement>
 *   </requiredPlugins>
 * </plugin>
 */
PluginDescription* readPluginDescriptionContent(QDomDocument& document)
{
    QDomElement rootElement = document.documentElement();

    if (rootElement.nodeName() != "plugin")
    {
        Framework::Instance.logger()->debug(__FILE__, __LINE__, "", "readPluginDescriptionContent", "unable to find root element plugin");
        return nullptr;
    }

    QString code, name, appGroups, libName;

    QDomAttr codeAttr = rootElement.attributeNode("code");
    if (codeAttr.isNull())
    {
        Framework::Instance.logger()->debug(__FILE__, __LINE__, "", "readPluginDescriptionContent", "unable to find code of element");
        return nullptr;
    }
    else
    {
        code = codeAttr.nodeValue().trimmed();
        libName = code;
    }

    QDomAttr libAttr = rootElement.attributeNode("libName");
    if (!libAttr.isNull())
    {
        libName = libAttr.nodeValue().trimmed();
    }

    QDomAttr nameAttr = rootElement.attributeNode("name");
    if (nameAttr.isNull())
        name = code;
    else
        name = nameAttr.nodeValue().trimmed();

    PluginVersion version, compatibleVersion;

    QDomElement versionElem = rootElement.firstChildElement("version");
    if (!versionElem.isNull())
    {
        version = PluginVersion::toVersion(versionElem.firstChild().nodeValue());
    }

    QDomElement compatibleVersionElem = rootElement.firstChildElement("compatibleVersion");
    if (!compatibleVersionElem.isNull())
    {
        compatibleVersion = PluginVersion::toVersion(compatibleVersionElem.firstChild().nodeValue());
    }

    QDomElement appGroupElem = rootElement.firstChildElement("appGroups");
    if (!appGroupElem.isNull() && appGroupElem.childNodes().count() > 0)
    {
        appGroups = appGroupElem.firstChild().nodeValue();
    }

    int startLevel = 100;
    QDomAttr startLevelAttr = rootElement.attributeNode("level");
    if (!startLevelAttr.isNull())
    {
        startLevel = startLevelAttr.nodeValue().trimmed().toInt();
    }

    QString logContent("code: %1, name: %2, version: %3, compatible version: %4, app. groups: %5, start level: %6, lib: %7");
    QString message = logContent.arg(code).arg(name).arg(version.toString()).arg(compatibleVersion.toString()).arg(startLevel).arg(libName);

    Framework::Instance.logger()->debug(__FILE__, __LINE__, "", "readPluginDescriptionContent", message);

    PluginDescription* pDescription = new PluginDescription(name, version, compatibleVersion);
    pDescription->setStartLevel(startLevel);
    pDescription->setCode(code);

    if (!appGroups.isEmpty())
    {
        appGroups = appGroups.trimmed();
        QStringList list = appGroups.split(",", QString::SkipEmptyParts);

        size_t count = list.size();
        for (size_t i = 0; i < count; i++)
        {
            pDescription->addApplicationGroup(list[i].trimmed());
        }
    }
    else
        pDescription->addApplicationGroup(PluginDescription::UNIVERSAL_APP_GROUP);

    QDomElement requirementElement = rootElement.firstChildElement("requiredPlugins");
    if (!requirementElement.isNull())
    {
        QDomNodeList nodes = requirementElement.elementsByTagName("requirement");
        QString requiredPluginName, requiredPluginVersion;
        for (int j = 0; j < nodes.count(); j++)
        {
            QDomNode child = nodes.at(j);
            if (child.isElement())
            {
                QDomElement element = child.toElement();

                requiredPluginName = element.attribute("code", "").trimmed();
                requiredPluginVersion = element.attribute("version", "").trimmed();

                Framework::Instance.logger()->debug(__FILE__, __LINE__, "", "readPluginDescriptionContent", QObject::tr("requirement code: ") + requiredPluginName + QObject::tr(", requirement version: ") + requiredPluginVersion);

                if (requiredPluginName.isEmpty() || requiredPluginVersion.isEmpty())
                    continue;

                PluginRequirement requirement(requiredPluginName, requiredPluginVersion);

                QDomElement serviceElement = element.firstChildElement("services");
                if (!serviceElement.isNull())
                {
                    QDomAttr serviceAttr = serviceElement.attributeNode("values");
                    if (!serviceAttr.isNull())
                    {
                        QString serviceNames = serviceAttr.nodeValue().trimmed();
                        Framework::Instance.logger()->debug(__FILE__, __LINE__, "", "readPluginDescriptionContent", QObject::tr("required services: ") + serviceNames);
                        QStringList serviceArray = serviceNames.split(",", QString::SkipEmptyParts);
                        requirement.setServices(serviceArray);
                    }
                }
                pDescription->addDependency(requirement);
            }
        }
    }
    return pDescription;
}

PluginDescriptionParser::PluginDescriptionParser()
{
}

PluginDescription *PluginDescriptionParser::parse(const QString &fileName)
{
    QFileInfo info(fileName);

    if (info.exists())
        return PluginDescriptionParser::parseFromFile(info);
    return nullptr;
}

void PluginDescriptionParser::parseDirectory(const QString &directoryName, vector<PluginDescriptionSharedPtr>& descriptions)
{
    QDir dir(directoryName);
    QFileInfoList list = dir.entryInfoList(QDir::NoDotAndDotDot|QDir::Files);

    QFileInfoList::iterator iter = list.begin();

    for (; iter != list.end(); iter++)
    {
        if ((*iter).suffix() == "xml")
        {
            PluginDescription* pDescription = PluginDescriptionParser::parseFromFile(*iter);
            if (nullptr != pDescription)
            {
                descriptions.push_back(PluginDescriptionSharedPtr(pDescription));
            }
        }
    }
}

PluginDescription *PluginDescriptionParser::parseFromFile(const QFileInfo &info)
{
    if (info.isDir() == false && info.suffix().toLower() == "xml") // is xml file
    {
        Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDescriptionParser", "parseFromFile", "parsing " + info.absoluteFilePath());
        // reads description
        QString name = info.absoluteFilePath();
        QFile description(name);
        QDomDocument document;
        if (!document.setContent(&description))
        {
            return nullptr;
        }
        PluginDescription* desc = readPluginDescriptionContent(document);
        Framework::Instance.logger()->debug(__FILE__, __LINE__, "PluginDescriptionParser", "parseFromFile", "parsed " + info.absoluteFilePath());
        if (nullptr != desc)
        {
            desc->setPath(desc->libName());
        }
        return desc;
    }
    return nullptr;
}

}
}

